package com.seventhdawn.gwt.rcx.client;

import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.InvocationException;
import com.google.gwt.user.client.rpc.IsSerializable;
import com.google.gwt.user.client.rpc.RpcRequestBuilder;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.client.rpc.SerializationStreamWriter;
import com.google.gwt.user.client.rpc.impl.RemoteServiceProxy;
import com.google.gwt.user.client.rpc.impl.Serializer;
import com.google.gwt.user.client.rpc.impl.RequestCallbackAdapter.ResponseReader;

/**
 * Client-side {@link com.google.gwt.user.client.rpc.RemoteService
 * RemoteService} proxy superclass that includes RPC context payload into each
 * RPC request.
 * <p>
 * RPC context payload is physically stored at the end of RPC request body. A
 * special {@link #HEADER_PAYLOAD_LENGTH header} is used to indicate the length
 * of RPC context payload.
 * <p>
 * <b>Important:</b> When using this proxy superclass, be sure to apply
 * {@link com.seventhdawn.gwt.rcx.server.RPCContextFilter RPCContextFilter}
 * <em>before your server-side RPC service implementation</em> (e.g. a
 * {@link com.google.gwt.user.server.rpc.RemoteServiceServlet
 * RemoteServiceServlet} subclass) takes over the RPC request processing.
 * 
 * @see RPCClientContext
 * @author vojtech.szocs
 */
public abstract class RPCContextServiceProxy extends RemoteServiceProxy {

    /**
     * Name of the HTTP header that holds the length of RPC context payload.
     */
    public static final String HEADER_PAYLOAD_LENGTH = "rpc-context-length";

    protected RPCContextServiceProxy(String moduleBaseURL, String remoteServiceRelativePath, String serializationPolicyName, Serializer serializer) {
        super(moduleBaseURL, remoteServiceRelativePath, serializationPolicyName, serializer);
    }

    /**
     * Configures a {@link RequestBuilder} to send an RPC request.
     * <p>
     * Each request includes the RPC context payload in addition to the
     * call-specific RPC payload.
     */
    protected <T> RequestBuilder doPrepareRequestBuilderImpl(ResponseReader responseReader, String methodName, int invocationCount,
            String requestData, AsyncCallback<T> callback) {
        if (getServiceEntryPoint() == null) {
            throw new NoServiceEntryPointSpecifiedException();
        }

        RequestCallback responseHandler = doCreateRequestCallback(responseReader, methodName, invocationCount, callback);

        ensureRpcRequestBuilder();

        rpcRequestBuilder.create(getServiceEntryPoint());
        rpcRequestBuilder.setCallback(responseHandler);
        rpcRequestBuilder.setContentType(RPC_CONTENT_TYPE);
        rpcRequestBuilder.setRequestId(invocationCount);

        // serialize RPC context object
        String rpcContextPayload = serializeContextObject(RPCClientContext.get());

        // append RPC payload to the actual request body
        StringBuilder requestDataBuilder = new StringBuilder(requestData);
        requestDataBuilder.append(rpcContextPayload);

        // update RPC request to include the payload
        rpcRequestBuilder.setRequestData(requestDataBuilder.toString());
        RequestBuilder rb = rpcRequestBuilder.finish();
        rb.setHeader(HEADER_PAYLOAD_LENGTH, String.valueOf(rpcContextPayload.length()));

        return rb;
    }

    protected String serializeContextObject(IsSerializable ctxObj) {
        SerializationStreamWriter streamWriter = createStreamWriter();

        try {
            streamWriter.writeObject(ctxObj);
        } catch (SerializationException ex) {
            throw new InvocationException("Unable to initiate the asynchronous service invocation -- error while serializing RPC context object", ex);
        }

        return streamWriter.toString();
    }

    // following code is copied (or overrided and copied) from
    // RemoteServiceProxy

    /**
     * The content type to be used in HTTP requests.
     */
    private static final String RPC_CONTENT_TYPE = "text/x-gwt-rpc; charset=utf-8";

    private RpcRequestBuilder rpcRequestBuilder;

    @Override
    protected <T> Request doInvoke(ResponseReader responseReader, String methodName, int invocationCount, String requestData,
            AsyncCallback<T> callback) {
        RequestBuilder rb = doPrepareRequestBuilderImpl(responseReader, methodName, invocationCount, requestData, callback);

        try {
            return rb.send();
        } catch (RequestException ex) {
            InvocationException iex = new InvocationException("Unable to initiate the asynchronous service invocation -- check the network connection", ex);
            callback.onFailure(iex);
        } finally {
            if (RemoteServiceProxy.isStatsAvailable()
                    && RemoteServiceProxy.stats(RemoteServiceProxy.bytesStat(methodName, invocationCount, requestData.length(), "requestSent"))) {
            }
        }

        return null;
    }

    @Override
    protected <T> RequestBuilder doPrepareRequestBuilder(ResponseReader responseReader, String methodName, int invocationCount, String requestData,
            AsyncCallback<T> callback) {
        RequestBuilder rb = doPrepareRequestBuilderImpl(responseReader, methodName, invocationCount, requestData, callback);

        return rb;
    }

    @Override
    public void setRpcRequestBuilder(RpcRequestBuilder builder) {
        this.rpcRequestBuilder = builder;
    }

    private void ensureRpcRequestBuilder() {
        if (rpcRequestBuilder == null) {
            rpcRequestBuilder = new RpcRequestBuilder();
        }
    }

}
